package com.cloudlead.common.persistence.impl;

import java.io.Serializable;
import java.util.List;

import javax.sql.DataSource;

import com.cloudlead.common.lang.Messages;
import com.cloudlead.common.persistence.*;
import com.mysema.query.sql.*;
import org.springframework.transaction.annotation.Transactional;

import com.cloudlead.common.lang.BusinessModule;
import com.cloudlead.common.lang.persistence.IdEntity;
import com.cloudlead.common.persistence.ParamAssert;
import com.mysema.query.sql.dml.SQLDeleteClause;
import com.mysema.query.sql.dml.SQLUpdateClause;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.Predicate;
import com.mysema.query.types.path.PathBuilder;

public abstract class AbstractQueryDslRepository<Pojo extends IdEntity<ID>, ID extends Serializable>
        extends AbstractInitialRepository<Pojo, ID> implements
        QueryDslRepository<Pojo, ID> {

    protected final int MAX_RECORDS = 1000;

    protected final EntityPathResolver entityPathResolver = SimpleEntityPathResolver.INSTANCE;
    protected final RelationalPath<Pojo> root;
    protected final PathBuilder<Pojo> builder;
    protected final SQLQueryFactory sqlQueryFactory;

    public AbstractQueryDslRepository(BusinessModule module,
                                      DataSource dataSource) {
        this(module, new QueryDslConfig(dataSource));
    }

    public AbstractQueryDslRepository(BusinessModule module, QueryDslConfig queryDslConfig) {
        super(module);
        this.root = entityPathResolver.createPath(entityClass);
        this.builder = entityPathResolver.getPathBuilder(root);
        sqlQueryFactory = queryDslConfig.getSqlQueryFactory();
    }

    @Override
    @Transactional(readOnly = true)
    public Pojo findOne(Predicate predicate) {
        try {
            SQLQuery query = sqlQueryFactory.from(root);
            if (null != predicate) {
                query.where(predicate);
            }
            return query.limit(1).uniqueResult(root);
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(Messages.getMessage("persistence.findOne.error"), e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    public List<Pojo> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
        try {
            SQLQuery query = sqlQueryFactory.from(root);
            if (null != predicate) {
                query.where(predicate);
            }
            if (null != orders && orders.length > 0) {
                query.orderBy(orders);
            }
            return query.limit(MAX_RECORDS).list(root);
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(Messages.getMessage("persistence.findAll.error"), e);
        }
    }

    @Override
    public List<Pojo> findAll(Predicate predicate, int offset, int limit,
                              OrderSpecifier<?>... orders) {
        try {
            SQLQuery query = sqlQueryFactory.from(root);
            if (null != predicate) {
                query.where(predicate);
            }
            if (null != orders && orders.length > 0) {
                query.orderBy(orders);
            }
            return query.offset(offset).limit(limit == 0 ? MAX_RECORDS : limit).list(root);
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(Messages.getMessage("persistence.findAll.error"), e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    public long count(Predicate predicate) {
        try {
            SQLQuery query = sqlQueryFactory.from(root);
            if (null != predicate) {
                query.where(predicate);
            }
            return query.count();
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(Messages.getMessage("persistence.count.error"), e);
        }
    }

    @Override
    @Transactional
    public long updateAll(Pojo entity, Predicate predicate) {
        ParamAssert.notNull(entity, "updateAll(entity,predicate) > entity ",
                this.getClass());
        ParamAssert.notNull(predicate,
                "updateAll(entity,predicate) > predicate ", this.getClass());
        try {
            SQLUpdateClause update = sqlQueryFactory.update(root).populate(
                    entity);
            update.where(predicate);
            return update.execute();
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(Messages.getMessage("persistence.updateAll.error"), e);
        }
    }

    @Override
    @Transactional
    public long deleteAll(Predicate predicate) {
        try {
            SQLDeleteClause delete = sqlQueryFactory.delete(root);
            if (null != predicate) {
                delete.where(predicate);
            }
            return delete.execute();
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(Messages.getMessage("persistence.deleteAll.error"), e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T find(Query query) {
        try {
            SQLQuery sqlQuery = sqlQueryFactory.from(root);
            return query.queryInTransactional(sqlQuery);
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(Messages.getMessage("persistence.query.error"), e);
        }
    }

}
